#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import signal
import logging
from bnet_util import Util
from bnet_param import WrtConst


class WrtObConnManager:

    # sub-host change should be dispatched in 10 seconds
    # vpn restart interval is 60 seconds

    def __init__(self, pluginHub, prefixPool, mntNs, netNs, callback):
        assert isinstance(callback, WrtObConnManagerCallback)

        self._pluginHub = pluginHub
        self._prefixPool = prefixPool
        self.mntNs = mntNs
        self.netNs = netNs
        self._cbObj = callback
        self._logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__)

        # avaiable connections
        self._availConnDict = dict()                                        # dict<connection-id,data>

    def start(self):
        for pobj in self._pluginHub.getObConnPluginList():
            pobj.run({
                # "socket-file": os.path.join(WrtConst.connectionPluginSocketFilePattern % (self._level)),         # FIXME: enter mntns and netns
            })

    def stop(self):
        for pobj in sorted(self._getMyPlugins(), reverse=True):
            pobj.terminate()
        assert len(self._availConnDict) == 0        # FIXME: terminate() is sync?
        self._logger.info("Terminated.")

    def get_obconn_id_list(self):
        return self._availConnDict.keys()

    def get_active_obconn_id_list(self):
        return [x for x, y in self._availConnDict.items() if y.active_data is not None]

    def has_active_connection(self):
        return len(self.get_active_obconn_id_list()) > 0

    def get_obconn_data(self, connection_id):
        assert connection_id in self._availConnDict

        pluginId = self._availConnDict[connection_id].pluginId
        pluginObj = self._pluginHub.getPluginById(pluginId)
        return pluginObj.get_connection(connection_id)

    def get_obconn_livedata(self, connection_id):
        assert connection_id in self._availConnDict
        assert connection_id in self._availConnDict[connection_id].active_data is not None

        return self._availConnDict[connection_id].active_data

    def enable_connection(self, connection_id):
        assert connection_id in self._availConnDict
        assert not self._pluginHub.getPluginById(self._availConnDict[connection_id]).is_obconn_enabled()

        self._pluginHub.getPluginById(self._availConnDict[connection_id]).enable_connection()

    def disable_connection(self, connection_id):
        assert connection_id in self._availConnDict
        assert self._pluginHub.getPluginById(self._availConnDict[connection_id]).is_obconn_enabled()

        self._pluginHub.getPluginById(self._availConnDict[connection_id]).disable_connection()

    def _obconn_available(self, plugin_id, connection_id):
        assert connection_id not in self._availConnDict

        # record
        cobj = _ManagerDataForObConn()
        cobj.pluginId = plugin_id
        self._availConnDict[connection_id] = cobj

        # notify upstream
        self._cbObj.on_obconn_available(plugin_id, connection_id)

    def _obconn_unavailable(self, connection_id, reason):
        assert connection_id in self._availConnDict
        assert connection_id in self._availConnDict[connection_id].active_data is None

        # notify upstream
        self._cbObj.on_obconn_unavailable(connection_id)

        # remove record
        del self._availConnDict[connection_id]

    def _obconn_activated(self, connection_id, connection_livedata):
        assert connection_id in self._availConnDict
        assert connection_id in self._availConnDict[connection_id].active_data is None

        # record live data
        self._availConnDict[connection_id].active_data = connection_livedata

        # set exclude prefix and restart if neccessary
        wanPrefixList = []
        for ifdata in connection_livedata.values():
            addr = ifdata["ifconfig"]["ipv4"]["address"]
            masklen = ifdata["ifconfig"]["ipv4"]["prefixlen"]
            wanPrefixList.append(Util.ipMaskToPrefix(addr, masklen))
        if self._prefixPool.setExcludePrefixList("wan-connection-%s" % (connection_id), wanPrefixList):
            os.kill(os.getpid(), signal.SIGHUP)
            raise Exception("bridge prefix duplicates with internet connection, autofix it and restart")

        # config interface
        for ifname, ifdata in connection_livedata.items():
            # FIXME
            pass
        # FIXME
        # interface may be down or disappears before activation finish, we should ignore this kind of error
        # plugin should ensure deactivation is called

        # notify upstream
        self._cbObj.on_obconn_activated(connection_id)

    def _obconn_deactivated(self, connection_id, reason):
        assert connection_id in self._availConnDict
        assert connection_id in self._availConnDict[connection_id].active_data is not None

        # notify upstream
        self._cbObj.on_obconn_deactivated(connection_id)

        # config interface (ignore if interface already disappeared)
        for ifname, ifdata in self._availConnDict[connection_id].active_data.items():
            # FIXME
            pass

        # remove exclude prefix
        self._prefixPool.removeExcludePrefixList("wan-connection-%s" % (connection_id))

        # remove live data
        self._availConnDict[connection_id].active_data = None



class _Container:

    def __init__(self):
        pass

class _ManagerDataForObConn:

    def __init__(self):
        self.pluginId = None
        self.active_data = None
